#include <cstdio>
#include <vector>
#include "pfs_item.h"
#include "pfs_functions.h"

//#define DEBUG

using std::vector;
using std::string;

typedef vector<Solution> (*MatingSelectFun)(const std::vector<Answer>&,  const int);
typedef vector<Solution> (*ReproduceFun)(const vector<Solution>&);
typedef Solution (*MutateFun)(const Solution&);
typedef Answer (*LocalSearchFun)(const Solution&, const PFS_Item&, clock_t time_limit);

void InitMatingSelectFuns
  (vector<MatingSelectFun>* mating_select_funs, vector<string>* function_names);

void InitReproduceFuns
  (vector<ReproduceFun>* reproduce_funs, vector<string>* function_names);

void InitMutateFuns
  (vector<MutateFun>* mutate_funs, vector<string>* function_names);

void InitLocalSearcFuns
  (vector<LocalSearchFun>* local_search_funs, vector<string>* function_names);

void InitFunctions();

static vector<MatingSelectFun> MatingSelectFuns;
static vector<ReproduceFun> ReproduceFuns;
static vector<MutateFun> MutateFuns;
static vector<LocalSearchFun> LocalSearchFuns;

static vector<string> mating_select_fun_names;
static vector<string> reproduce_fun_names;
static vector<string> mutate_fun_names;
static vector<string> local_search_fun_names;

PFS_Item::JMTable ReadFile(FILE* fptr);
FILE* of_ptr = stdout;

void TestLocalSearch(const PFS_Item& pfs_item) {
  vector<Solution> population = InitPopulation(pfs_item, 20);

  printf("Start test ls\n");

  for(size_t i = 0 ; i < LocalSearchFuns.size() ; ++i) {
    puts(local_search_fun_names[i].c_str());
    printf("\n");
    for(size_t j = 0 ; j < population.size() ; ++j)
      printf("%d \n", (*LocalSearchFuns[i])(population[j], pfs_item, 100).score_);
  }
}

bool D100(int r) {
  r %= 100;
  return (rand() % 100) <= r;  
}

void MA(const PFS_Item& pfs_item, size_t p_size, clock_t lc_time_limit, int max_g,
        MatingSelectFun MatingSelect, ReproduceFun Reproduce,
        MutateFun Mutate, LocalSearchFun LocalSearch);

int main(int argc, char** argv) {
  InitFunctions();

  srand(static_cast<unsigned>(time(NULL)));
  
  FILE* testdata_ptr = fopen("inputs/tai50_10_1.txt", "r");
  if(!testdata_ptr) { perror("open file faild\n"); }
  of_ptr = fopen("outputs/tai50_10_1_out_nor_5.csv", "w");
  if(!of_ptr) { perror("open file faild\n"); }

  
  PFS_Item pfs_item(ReadFile(testdata_ptr));
  fclose(testdata_ptr) ;

  size_t i = 0, j=2,k=1;
  for(size_t population_size = 10;population_size <= 100 ; population_size*=10) {
    for (int c = 0 ; c < 20 ; ++c) {
      fprintf(of_ptr, "%s,%s,%s", 
        mating_select_fun_names[i].c_str(),
        reproduce_fun_names[j].c_str(),
        mutate_fun_names[k].c_str());
      clock_t s = clock();
      MA(pfs_item, population_size, 10, 100,
          MatingSelectFuns[i], ReproduceFuns[j],
          MutateFuns[k], &SALocalSearch);
      double spt = (double)(clock()-s) / CLOCKS_PER_SEC;
      fprintf(of_ptr, ",%lf\n", spt);
    }
  }
  /*
  for (size_t i = 1 ; i <  MatingSelectFuns.size() ; ++i) {
    for (size_t j = 0 ; j <  ReproduceFuns.size() ; ++j) {
      for (size_t k = 0 ; k <  MutateFuns.size() ; ++k) {
        fprintf(of_ptr, "%s,%s,%s", 
          mating_select_fun_names[i].c_str(),
          reproduce_fun_names[j].c_str(),
          mutate_fun_names[k].c_str());
        MA(pfs_item, population_size, 10, 1000,
            MatingSelectFuns[i], ReproduceFuns[j],
            MutateFuns[k], &SALocalSearch);
        fprintf(of_ptr, "\n");
      }
    }
  }
  */
  

  fclose(of_ptr) ;
}

vector<Answer> merge(const vector<Answer>& a, const vector<Answer>& b) {
  vector<Answer> AB;
  AB.reserve(a.size() + b.size());
  AB.insert( AB.end(), a.begin(), a.end() );
  AB.insert( AB.end(), b.begin(), b.end() );
  return AB;
}

int BestScore(const vector<Answer>& a) {
  int b = INT_MAX;
  for (auto e : a) {
    if (e.score_ < b) b = e.score_;
  }
  return b;
}

void MA(const PFS_Item& pfs_item, size_t p_size, clock_t lc_time_limit, int max_g,
        MatingSelectFun MatingSelect, ReproduceFun Reproduce,
        MutateFun Mutate, LocalSearchFun LocalSearch) {
  vector<Solution> population = InitPopulation(pfs_item, p_size);

  for (int g = 0 ; g < max_g ; ++g) {    
    
    for(auto& p : population) {
      if(D100(5)) { p = Mutate(p); }
    }

    vector<Answer> evaluateds = Evaluate(population, pfs_item);

    {
      printf("%d\n", g);
      fprintf(of_ptr, ",%d", BestScore(evaluateds));        
    }

  
    auto parents = ((*MatingSelect)(evaluateds, evaluateds.size()/2));

    vector<Answer> children = Evaluate((*Reproduce)(parents), pfs_item);

    auto selecteds = EnvironmentalSelection(merge(children, evaluateds), p_size);
    
    for(auto& e : selecteds) {
      if(D100(5)) {
        e = LocalSearch(e, pfs_item, lc_time_limit).solution_;
      }
    }

    population = selecteds;
  }

}

PFS_Item::JMTable ReadFile(FILE* fptr) {
  int machine_num, job_num;
  fscanf(fptr, "%d %d %*s", &job_num, &machine_num);

  PFS_Item::JMTable table(machine_num, std::vector<int>(job_num));
  for (auto& machine : table) {
    for (auto& job : machine) {
      fscanf(fptr, "%d", &job);
    }
  }

  return table;
}

void InitMatingSelectFuns
    (vector<MatingSelectFun>* mating_select_funs, vector<string>* function_names) {
  mating_select_funs->push_back(&TournamentSelection);
  function_names->push_back("Tournament");

  mating_select_funs->push_back(&WindowingRouletteMatingSelection);
  function_names->push_back("Windowing Roulette");
}


void InitReproduceFuns
    (vector<ReproduceFun>* reproduce_funs, vector<string>* function_names) {
  reproduce_funs->push_back(&OXReproduce);
  function_names->push_back("OX");

  reproduce_funs->push_back(&LOXReproduce);
  function_names->push_back("LOX");

  reproduce_funs->push_back(&CycleReproduce);
  function_names->push_back("Cycle");

  reproduce_funs->push_back(&PMReproduce);
  function_names->push_back("PM");

  reproduce_funs->push_back(&POXReproduce);
  function_names->push_back("POX");

  reproduce_funs->push_back(&PPXReproduce);
  function_names->push_back("PPX");
}

void InitMutateFuns
    (vector<MutateFun>* mutate_funs, vector<string>* function_names) {
  mutate_funs->push_back(&RandomSwapMutate);
  function_names->push_back("RandomSwap");

  mutate_funs->push_back(&RandomInsertMutate);
  function_names->push_back("InsertMutate");
}

void InitLocalSearcFuns
    (vector<LocalSearchFun>* local_search_funs, vector<string>* function_names) {
  local_search_funs->push_back(&IILocalSearch);
  function_names->push_back("II");

  local_search_funs->push_back(&TSLocalSearch);
  function_names->push_back("TS");

  local_search_funs->push_back(&SALocalSearch);
  function_names->push_back("SA");

  
}


void InitFunctions() {
  InitMatingSelectFuns(&MatingSelectFuns, &mating_select_fun_names);
  InitReproduceFuns(&ReproduceFuns, &reproduce_fun_names);
  InitMutateFuns(&MutateFuns, &mutate_fun_names);
  InitLocalSearcFuns(&LocalSearchFuns, &local_search_fun_names);
}